package org.log4jconfig.xml.editor.utils;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.Flags;
import org.eclipse.jdt.core.IClassFile;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IField;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.ITypeHierarchy;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.Signature;
import org.eclipse.jdt.internal.core.CreateTypeHierarchyOperation;
import org.eclipse.wst.sse.core.StructuredModelManager;
import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;

/**
 * A set of utilities / wrapper methods for the JDT
 */
@SuppressWarnings("restriction")
public final class JdtUtils {

	/**
	 * Retrieve the JavaProject the current edited resource belongs to.
	 * 
	 * @param document the current document under edition
	 * @return instance of IJavaProject containing the edited resource
	 */
	public static IJavaProject getJavaProject(final IStructuredDocument document) {
		if(document == null) {
			return null;
		}
		IFile resource = getFile(document);
		IJavaProject project = JavaCore.create(resource.getProject());
		return project;
	}

	/**
	 * Gets the file associated to the given document.
	 * 
	 * @param document the document
	 * @return the file
	 */
	public static IFile getFile(final IStructuredDocument document) {
		IStructuredModel model = StructuredModelManager.getModelManager().getModelForEdit(document);
		IFile resource = null;
		try {
			String baselocation = model.getBaseLocation();
			if (baselocation != null) {
				IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
				IPath filePath = new Path(baselocation);
				if (filePath.segmentCount() > 0) {
					resource = root.getFile(filePath);
				}
			}
		} finally {
			if (model != null) {
				model.releaseFromEdit();
			}
		}
		return resource;
	}

	/**
	 * Returns the IType given its fully qualified name, found in the current
	 * java project's classpath.
	 * 
	 * @param javaProject the current Java Project in the workspace
	 * @param fullyQualifiedName the fully qualified given name of the type
	 * @return the instance of IType
	 * @throws JavaModelException thrown by the method at the JDT API level
	 */
	public static IType getTypeFromCurrentProject(final IJavaProject javaProject, final String fullyQualifiedName)
			throws JavaModelException {
		return javaProject.findType(fullyQualifiedName);

	}

	public static ITypeHierarchy getTypeHierarchy(IJavaProject javaProject, IType type) throws JavaModelException {
		CreateTypeHierarchyOperation operation = new CreateTypeHierarchyOperation(type, null, javaProject, true);
		ITypeHierarchy hierarchy = operation.getResult();
		if (hierarchy.exists()) {
			hierarchy.refresh(new NullProgressMonitor());
		}
		return hierarchy;
	}

	public static List<String> getClassesFromTypeHierarchy(IJavaProject javaProject, IType type,
			ITypeHierarchy hierarchy) throws JavaModelException {
		List<String> values = new ArrayList<String>();
		if (hierarchy != null && hierarchy.exists()) {
			// only keep concrete classes from the retrieved type hierarchy.
			for (IType t : hierarchy.getAllSubtypes(type)) {
				if (t.isClass() && !Flags.isAbstract(t.getFlags())) {
					values.add(t.getFullyQualifiedName());
				}
			}
			// sort proposal values
			Collections.sort(values);
		}
		return values;

	}

	public static String getPackageFromQualifiedName(String qName) {
		if (qName == null || qName.equals("") || qName.equals(".")) {
			return "";
		}
		if (qName.charAt(qName.length() - 1) == '.') {
			return qName.substring(0, qName.length() - 1);
		} else {
			int lastDotIndex = qName.lastIndexOf('.');
			// if next char is uppercase, then it is the beginning of a
			// className (according to Java coding conventions)
			if (lastDotIndex == -1 && Character.isUpperCase(qName.charAt(0))) {
				return "";
			} else if (lastDotIndex == -1 && !Character.isUpperCase(qName.charAt(0))) {
				return qName;
			} else if (Character.isUpperCase(qName.charAt(lastDotIndex + 1))) {
				return qName.substring(0, lastDotIndex);
			}
		}
		return qName;

	}

	public static List<IField> getStaticFieldsFromType(IType type) throws JavaModelException {
		List<IField> fields = new ArrayList<IField>();
		String typeSignature = Signature.createTypeSignature(type.getFullyQualifiedName(), true);
		for (IField field : type.getFields()) {
			if (Flags.isFinal(field.getFlags()) && Flags.isStatic(field.getFlags()) && Flags.isPublic(field.getFlags())
					&& field.getDeclaringType().equals(type) && field.getTypeSignature().equals(typeSignature)) {
				fields.add(field);
			}

		}
		return fields;
	}

	public static List<String> getAllSourcePackages(IJavaProject javaProject) {
		List<String> packages = new ArrayList<String>();
		try {
			IPackageFragmentRoot[] packageFragmentRoots = javaProject.getAllPackageFragmentRoots();
			for (IPackageFragmentRoot fragmentRoot : packageFragmentRoots) {
				if (fragmentRoot.getKind() == IPackageFragmentRoot.K_SOURCE) {
					for (IJavaElement element : fragmentRoot.getChildren()) {
						IPackageFragment fragment = (IPackageFragment) element;
						String name = fragment.getElementName();
						// exclude default package and avoid duplicate values
						if (!name.equals(IPackageFragment.DEFAULT_PACKAGE_NAME) && !packages.contains(name)) {
							packages.add(name);
						}
					}
				}
			}
		
		} catch(JavaModelException e) {
			Logger.error("Caught error while trying to find all source packages", e);
			Collections.sort(packages);
		}
		return packages;
	}

	public static List<String> getPackagesFromFragmentRoots(IPackageFragmentRoot[] packageFragmentRoots)
			throws JavaModelException {
		List<String> packages = new ArrayList<String>();
		for (IPackageFragmentRoot fragmentRoot : packageFragmentRoots) {
			if(fragmentRoot.getRawClasspathEntry().getPath().toString().equals("org.eclipse.jdt.launching.JRE_CONTAINER")) {
				continue;
			}
			for (IJavaElement element : fragmentRoot.getChildren()) {
				IPackageFragment fragment = (IPackageFragment) element;
				String name = fragment.getElementName();
				// exclude default package and avoid duplicate values
				if (!name.equals(IPackageFragment.DEFAULT_PACKAGE_NAME) && !packages.contains(name)) {
					packages.add(name);
				}
			}
		}
		Collections.sort(packages);
		return packages;
	}

	public static List<String> getClassesFromFragmentRoots(IPackageFragmentRoot[] packageFragmentRoots,
			String matchingPackage) throws JavaModelException {
		List<String> classes = new ArrayList<String>();
		for (IPackageFragmentRoot fragmentRoot : packageFragmentRoots) {
			for (IJavaElement element : fragmentRoot.getChildren()) {
				IPackageFragment fragment = (IPackageFragment) element;
				String name = fragment.getElementName();

				// for current package only, add all the available
				// classes
				if (matchingPackage != null && matchingPackage.equals(name)) {
					// package fragment with java classes
					if (fragment.getKind() == IPackageFragmentRoot.K_BINARY) {
						for (IClassFile classFile : fragment.getClassFiles()) {
							classes.add(classFile.getType().getFullyQualifiedName());
						}
					}
					// package fragment with java source
					// TODO : check if this also works with inner classes ?
					else {
						for (ICompilationUnit item : fragment.getCompilationUnits()) {
							classes.add(item.findPrimaryType().getFullyQualifiedName());
						}
					}
				}
			}

		}
		Collections.sort(classes);
		return classes;
	}

	public static Map<String, String> getMethodsWithJavadocFromType(IType type) throws JavaModelException {
		Map<String, String> proposals = new HashMap<String, String>();
		while (type != null) {
			for (IMethod method : type.getMethods()) {
				if (Flags.isPublic(method.getFlags()) && !Flags.isSynchronized(method.getFlags())
						&& !Flags.isAbstract(method.getFlags()) && method.getElementName().indexOf("set") == 0
						&& method.getParameterTypes().length == 1) {
					String value = method.getElementName().substring("set".length());
					proposals.put(value, method.getAttachedJavadoc(null));

				}
			}
			if (type.getSuperclassName() != null) {
				type = type.getJavaProject().findType(type.getSuperclassName());
			} else {
				type = null;
			}
		}
		return proposals;
	}

	public static Set<String> getSettableAttributes(IType type) throws JavaModelException {
		Set<String> methods = new HashSet<String>();
		while (type != null) {
			for (IMethod method : type.getMethods()) {
				if (Flags.isPublic(method.getFlags()) && !Flags.isSynchronized(method.getFlags())
						&& !Flags.isAbstract(method.getFlags()) && method.getElementName().indexOf("set") == 0
						&& method.getParameterTypes().length == 1) {
					methods.add(method.getElementName().substring("set".length()));
				}
			}
			if (type.getSuperclassName() != null) {
				type = type.getJavaProject().findType(type.getSuperclassName());
			} else {
				type = null;
			}
		}
		return methods;
	}

}
